package spl.army;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import spl.utils.FileProperties;
import spl.utils.GlobalLogger;

public class Board {
	// Array of missions that the board is initialized with.
	private static ArrayList<Mission> missionsList = new ArrayList<Mission>();
	// A Hash of missions, mapped by it's name
	private static HashMap<String, Mission> missionsHash = new HashMap<String, Mission>();
	
	/**
	 * Disable the default constructor
	 **/
	private Board()
	{
	}
	
	/**
	 * Clears every mission
	 */
	public static void resetBoard() {
		Board.missionsList = new ArrayList<Mission>();
		Board.missionsHash = new HashMap<String, Mission>();
	}
	
	/**
	 * Fill a new Board with missions from a given mission file
	 * @param missionsPropFilename
	 * 			path to missions properties file
	 * 
	 * @throws ParseException if the file is in invalid format
	 * @throws FileNotFoundException if can't locate file on disk
	 * @throws IOException on any other IO exceptions
	 **/	
	public static void initBoardFromPropFile(String missionsPropFilename) throws ParseException, FileNotFoundException, IOException
	{
		FileProperties fp = null;
		fp = new FileProperties(missionsPropFilename);

		String numberOfMissionStr = fp.getProperty("numberOfMissions");
		if(null==numberOfMissionStr) {		
			throw new ParseException("no attribute 'numberOfMissions' in the given properties file", 0);
		}
		
		Integer numberOfMissions = Integer.decode(numberOfMissionStr.trim());
		GlobalLogger.getLogger().finest("Found " + numberOfMissions + " missions");
		
		for(int i=0; i<numberOfMissions; i++){
			String name = fp.getProperty(i, "m", "Name");
			String skill = fp.getProperty(i, "m", "Skill");
			String timeStr = fp.getProperty(i, "m", "Time");
			String itemsStr = fp.getProperty(i, "m", "Items");
			
			// Fill it later
			ArrayList<Mission> preReqMissions = new ArrayList<Mission>();
			Mission newMission = new Mission(name, skill, timeStr, itemsStr, preReqMissions);
			Board.addMission(newMission);
		}
		
		
		for(int i=0; i<numberOfMissions; i++){
			String preRequisitesStr = fp.getProperty(i, "m", "PreRequisites");
			List<Mission> reqMissions = Board.getMissionListFromString(preRequisitesStr);
			Mission current = Board.missionsList.get(i);
			current.addPreRequest(reqMissions);
		}
	}
	
	/**
	 * Iterate over the missions and find out if they can be started
	 * @return a List of all possible missions that can be executed.
	 */
	public static List<Mission> getReadyToExecuteMissions()
	{
		Iterator<Mission> it = Board.missionsList.iterator();
		Mission currentMission;
		ArrayList<Mission> result = new ArrayList<Mission>();
		while(it.hasNext()){
			currentMission = it.next();
			if(!currentMission.isActive() && currentMission.checkPrerequest() && currentMission.getTimeLeft()>0)
				result.add(currentMission);
		}
		return result;
	}
	
	/**
	 * Returns a list of Mission objects from the board, referred by
	 * the preRequest string list.
	 * 
	 * @param preRequest string of missions, i.e: "mission1 mission2", "mission1, mission2"
	 * @return list of Mission("mission1"), Mission("mission2")
	 */
	public static List<Mission> getMissionListFromString(String preRequest){
		ArrayList<Mission> missions = new ArrayList<Mission>();
		
		// Safety checks that the preRequest missions is not an empty string
		preRequest.trim();
		if(preRequest.length() == 0)
			return missions;
		
		// Parse missions string
		ArrayList<String> missionsNames = new ArrayList<String>(
				Arrays.asList(preRequest.split("[,\\s]+"))
				);
		
		// Iterate over the parsed missions, and add them
		Iterator<String> nextMission = missionsNames.iterator();

		while(nextMission.hasNext()){
			missions.add(Board.missionsHash.get(nextMission.next().trim()));
		}
		return missions;
	}

	/**
	 * Get Mission object that has the name 'name'
	 * @param name of the mission
	 * @return Mission object associated with that name
	 */
	public static Mission getMissionByName(String name) {
		return Board.missionsHash.get(name);
	}
	
	/**
	 * Add's a new mission to the board. Get missions parameters
	 * @param name mission's name
	 * @param reqSkill required skills for the mission
	 * @param reqTime time that the missions takes
	 * @param preReqStr string represents the 
	 * @param reqItems list of ItemRequired elements that represent the require elements.
	 */
	public static void addMission(String name, String reqSkill, int reqTime, String preReqStr, List<ItemRequired> reqItems)
	{
		List<Mission> preReq = Board.getMissionListFromString(preReqStr);
		Board.addMission(name, reqSkill, reqTime, preReq, reqItems);
	}	
	
	/**
	 * Adds a new mission
	 * @param name mission's name
	 * @param reqSkill required skills for the mission
	 * @param reqTime time that the missions takes
	 * @param preReq list represents the 
	 * @param reqItems list of ItemRequired elements that represent the require elements.
	 */
	public static void addMission(String name, String reqSkill, int reqTime, List<Mission> preReq, List<ItemRequired> reqItems)
	{
		Mission newMission = new Mission(name, reqSkill, reqTime, reqItems, preReq);
		Board.addMission(newMission);
	}
	
	/**
	 * Adds new mission.
	 * The function translates strings of missions and items to java objects.
	 * 
	 * @param name mission's name
	 * @param reqSkill required skills for the mission
	 * @param reqTime time that the missions takes
	 * @param preRequiredMissionsStr represents the required items. i.e: "mission1, mission2" or "mission1 mission2" 
	 * @param reqItems represents the elements that required. i.e: "item1, item2" or "item1 item2".
	 */
	public static void addMission(String name, String reqSkill, String reqTime,
			String preRequiredMissionsStr, String reqItems) {

		List<Mission> preReqMissionsLst;
		if (null == preRequiredMissionsStr)
			preReqMissionsLst = null;
		else
			preReqMissionsLst = getMissionListFromString(preRequiredMissionsStr);
		
		
		Mission newMission = new Mission(name, reqSkill, reqTime, reqItems, preReqMissionsLst);
		
		Board.addMission(newMission);
	}
	
	/** 
	 * @param newMission is a mission that we add to the board
	 */
	public static void addMission(Mission newMission) {
		GlobalLogger.getLogger().finest("Adding mission " + newMission.toString() +  " to the board");
		// We rather not to lock newMission because this function will be called
		// only from the observer thread - the same thread that performs queries etc.
		Board.missionsList.add(newMission);
		Board.missionsHash.put(newMission.getMissionName(), newMission);
		
		ChiefOfStaff.updateFromBoard();
	}
	
	/**
	 * Notify the board that a mission is done. 
	 * @param currentMission is the mission that's state was changed.
	 */
	public static void updateMission(Mission currentMission) {
		ChiefOfStaff.updateFromBoard();
	}
	
	/**
	 * @return unmodified view of the missions
	 */
	public static List<Mission> getMissions() {
		// Return immutable of the mission list
		return Collections.unmodifiableList(Board.missionsList);
	}
	
	/**
	 * Checks if the game is over
	 * @return false iff there is no less than one active mission
	 */
	public static boolean isAllDone(){
		// Mission is a stand-alone object. we don't want to lock all missions
		Iterator<Mission> it = Board.missionsList.iterator();
		while(it.hasNext()){
			// the next call is synchronized 
			if(it.next().getTimeLeft() != 0)
				return false;
		}
		return true;
	}
	
	/**
	 * Iterate over missions, and display their status.
	 */
	public static void reportIncompleteMissions() {
		Formatter formatter = new Formatter();

		// First - Lock all missions
		GlobalLogger.getLogger().info("Locking all missions");
		synchronized(missionsList) {
			for (Mission m : Board.getMissions()) {
				// Check if this mission is incomplete
				if (m.getTimeLeft() != 0) {
					formatter.format("[ ] %s\n", m.toString());
					
					// Iterate over the prerequisite missions that weren't completed yet
					List<Mission> preRequisits = m.getPreReqMissions(); 
					if (null != preRequisits) {
						for (Mission preMission : preRequisits)
							if (null != preMission)
								formatter.format("\t%s\n", preMission.toString());
					}
				}
			}
			GlobalLogger.getLogger().info("Unlocking missions");
		}
		
		System.out.print(formatter.toString());
	}
	
	/**
	 * Iterate over missions, and display their status.
	 */
	public static void reportCompleteMissions() {
		Formatter formatter = new Formatter();

		// First - Lock all missions
		GlobalLogger.getLogger().info("Locking all missions");
		synchronized(missionsList) {
			for (Mission m : Board.getMissions()) {
				// Check if this mission is incomplete
				if (m.getTimeLeft() == 0) {
					formatter.format("[V] %s - ", m.toString());
					try {
						formatter.format ("%s\n" , m.getAssignedSergeant().getName());
					} catch (IllegalStateException e) {
						formatter.format ("???\n");
					}
				}
			}
			GlobalLogger.getLogger().info("Unlocking missions");
		}
		
		System.out.print(formatter.toString());
	}
}
